<?php

namespace p5\http;


use CURLFile;
use p5\objects\FileUtils;
use p5\objects\RequestUtils;

class HttpClient
{
    const PARAMETER_TYPE_NORMAL = "normal";
    const PARAMETER_TYPE_XML = "xml";
    const PARAMETER_TYPE_JSON = "json";
    const PARAMETER_TYPE_C5 = "c5";

    protected $headers = array();
    protected $parameters = array();
    protected $files = array();
    protected $debug = false;
    protected $track = false; //如果跟踪的话，在fullUrl后面会加上"?XDEBUG_SESSION_START=PHPSTORM"
    protected $baseUrl = "";
    protected $url = ""; // 调用的地址
    protected $parameterType = HttpClient::PARAMETER_TYPE_NORMAL;
    protected $result = "";
    protected $errorNo = "";
    protected $errorMessage = "";
    protected $method = "POST";
    protected $executeTimeoutSeconds = 600;
    protected $connectTimeoutSeconds = 600;
    protected $sslVerifyHost = false;
    protected $sslVerifyPeer = false;
    protected $sslCerts = [];
    protected $curlLogFile = "";
    protected $escapeJsonUnicode = false;
    protected $logs = "";

    public function call($method = "POST")
    {
        if (!empty($method)) {
            $this->method = $method;
        }
        $this->curl_call();
    }

    public function curl_call()
    {
        $this->debug("call url ", $this->getFullUrl());
        $this->debug("method", $this->method);
        $this->debug("header", $this->buildHeaders());

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->getFullUrl());
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->buildHeaders());
        // curl_setopt($ch, CURLOPT_REFERER, "http://www.163.com/ ");   //构造来路
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->executeTimeoutSeconds);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeoutSeconds);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
        //  curl_setopt($ch, CURLOPT_UPLOAD, empty($this->files));
        curl_setopt($ch, CURLOPT_BINARYTRANSFER, !empty($this->files));
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        if (empty($this->files) && $this->parameterType == HttpClient::PARAMETER_TYPE_JSON) {
            $data_string = $this->getPostData();
            // echo $data_string ;
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
            $this->debug("post data", $data_string);
        } else if (empty($this->files)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getPostData());
            $this->debug("post data", $this->getPostData());
        } else {
            // has file
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getPostData());
            $this->debug("post with file ", $this->getPostData());
        }

        $string = curl_exec($ch);
        $this->debug("result raw info", $string);

        if (curl_errno($ch)) {    //出错则显示错误信息
            $this->errorNo = curl_errno($ch);
            $this->errorMessage = curl_error($ch);
            $this->debug("curl errors", $this->errorMessage);
        }
        curl_close($ch);
        if (ord($string[0]) == 239 && ord($string[1]) == 187 && ord($string[2]) == 191) {
            $string = substr($string, 3);
        }
        $this->result = $string;
        $this->debug("final result", $this->result);
    }

    public function getPostData()
    {
        if (empty($this->files) && $this->parameterType == HttpClient::PARAMETER_TYPE_JSON) {
            if ($this->escapeJsonUnicode) {
                return json_encode($this->parameters, JSON_UNESCAPED_UNICODE);
            } else {
                return json_encode($this->parameters);
            }
        }
        if (empty($this->files)) {
            return http_build_query($this->parameters);
        }
        $args = array_merge(array(), $this->parameters);
        foreach ($this->files as $file) {
            $args[$file["post_name"]] = new CURLFile($file["file"], $this->getFileMimeType($file["file"]), $file["file_name"]);
        }
        return $args;
    }

    public function getFromUrl()
    {
        $file = file_get_contents($this->getFullUrl());

        $this->debug("getFromUrl ", $file);
        $this->result = $file;
    }

    public function curlGet()
    {
        $this->result = $this->doHttpGet($this->getFullUrl());
    }

    protected function doHttpGet($url, $index = 0)
    {
        FileUtils::rewrite_file("e:/temp/aaa.txt", $url);
        $oCurl = curl_init();
        if (stripos($url, "https://") !== FALSE) {
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
            curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
        }
        curl_setopt($oCurl, CURLOPT_URL, $url);
        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
        $sContent = curl_exec($oCurl);
        $aStatus = curl_getinfo($oCurl);
        curl_close($oCurl);
        if (intval($aStatus["http_code"]) == 200) {
            $string = $sContent;
            return $string;
        } else {
            return false;
        }
    }


    public function getFileMimeType($file)
    {
        if (function_exists("mime_content_type")) {
            return mime_content_type($file);
        }
        if (function_exists("finfo_open")) {
            $info = finfo_open(FILEINFO_MIME_TYPE);
            $bean = finfo_file($info, $file);
            //    finfo_close($info);
            return $bean;
        }
        return MimeUtils::getMimeType($file);
    }

    public function buildHeaders()
    {
        $this->initContentType();
        $list = array();
        foreach ($this->headers as $key => $value) {
            if (empty($value)) {
                continue;
            }
            array_push($list, "$key: $value");
        }
        return $list;
    }

    public function getFullUrl()
    {
        $url = $this->url;
        if (!empty($this->baseUrl)) {
            if (!$this->startsWith(strtolower($url), strtolower($this->baseUrl))) {
                $fullUrl = $this->baseUrl . $url;
                if ($this->track) {
                    $fullUrl .= "?XDEBUG_SESSION_START=PHPSTORM";
                }
                $url = $fullUrl;
            }
        }
        if ($this->isGet()) {
            $url = RequestUtils::buildLink($url, $this->parameters);
        }

        return $url;
    }

    public function getFullUrlWithArgs()
    {
        $m = $this->getMethod();
        $this->setMethod("GET");
        $url = $this->getFullUrl();
        $this->setMethod($m);
        return $url;
    }


    public function isHttps()
    {
        $temp = strtolower($this->getFullUrl());
        return strpos($temp, "https://") === 0;
    }

    private function getPostXmlData()
    {
        return static::arrayToXml($this->getParameters());
    }

    public function arrayToXml($arr)
    {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if (is_array($val)) {
                $xml .= "<" . $key . ">" . ($this->arrayToXml($val)) . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            }
        }
        $xml .= "</xml>";

        $string = str_replace("<", "&lt;", $xml);
        $string = str_replace(">", "&gt;", $string);
        $this->debug("HttpClient", $string);
        return $xml;
    }

    public function isPost()
    {
        return $this->method === "POST";
    }

    public function isGet()
    {
        return $this->method === "GET";
    }

    public function get()
    {
        $this->call("GET");
    }

    public function post()
    {
        $this->call("POST");
    }

    public function delete()
    {
        $this->call("DELETE");
    }

    public function put()
    {
        $this->call("PUT");
    }

    public function getResult()
    {
        return $this->result;
    }

    public function getResultArray()
    {
        if (is_array($this->result)) {
            return $this->result;
        }
        return json_decode($this->result, true);
    }

    public function buildContent()
    {
        if ($this->parameterType == HttpClient::PARAMETER_TYPE_JSON) {
            return json_encode($this->parameters);
        } else {
            return http_build_query($this->parameters);
        }

    }

    public function initContentType()
    {
        if ($this->hasHeader("content-type")) {
            return;
        }
        if (!empty($this->files)) {
            $this->add_header("content-type", "multipart/form-data");
            return;
        }
        if ($this->parameterType == HttpClient::PARAMETER_TYPE_JSON) {
            $this->add_header("content-type", "application/json");
        } else if ($this->parameterType == HttpClient::PARAMETER_TYPE_XML) {
            $this->add_header("content-type", "application/xml");
        } else {
            $this->add_header("content-type", "application/x-www-form-urlencoded");
        }
    }

    private function isFormUrlEncoded()
    {
        if (!array_key_exists("content-type", $this->headers)) {
            return false;
        }
        return $this->headers["content-type"] === "application/x-www-form-urlencoded";
    }

    public function get_milli_time()
    {
        $time_test = microtime();
        list($ten_thousand, $sec) = explode(" ", $time_test);
        $time = ((float)$ten_thousand + (float)$sec);
        return round($time * 1000);
    }

    public function debug($tag, $target)
    {
        global $logger;
        if ($this->debug !== true) {
            return;
        }
        $info = $target;
        if (is_array($target) || is_object($target)) {
            $info = json_encode($target);
        }
        $info = "\r\nc5_client debug: " . $tag . " : " . $info;
        $this->logs .= $info ;
    }

    public function getLogs(){
        return $this->logs;
    }

    public function add_parameter($name, $value)
    {
        $this->parameters[$name] = $value;
    }

    public function addParameter($name, $value)
    {
        $this->add_parameter($name, $value);
    }

    public function addParameters($parameters)
    {
        $this->add_parameters($parameters);
    }

    public function add_parameters($parameters)
    {
        foreach ($parameters as $key => $value) {
            if (empty($key)) {
                continue;
            }
            if (is_array($value)) {
                $value = json_encode($value);
            }
            $this->add_parameter($key, $value);
        }
    }


    public function add_header($name, $value)
    {
        $this->headers[$name] = $value;
    }

    public function add_headers($headers)
    {
        $this->headers = array_merge($this->headers, $headers);
    }

    public function addHeaders($headers)
    {
        $this->add_headers($headers);
    }

    public function addHeader($name, $value)
    {
        $this->add_header($name, $value);
    }

    public function hasHeader($name)
    {
        return array_key_exists($name, $this->headers);
    }

    public function getHeader($name)
    {
        if ($this->hasHeader($name)) {
            return $this->headers[$name];
        }
        return "";
    }

    /**
     * @return array
     */
    public function getHeaders()
    {
        return $this->headers;
    }

    /**
     * @param array $headers
     */
    public function setHeaders($headers)
    {
        $this->headers = $headers;
    }

    /**
     * @return array
     */
    public function getParameters()
    {
        return $this->parameters;
    }

    /**
     * @param array $parameters
     */
    public function setParameters($parameters)
    {
        $this->parameters = $parameters;
    }

    /**
     * @return boolean
     */
    public function isDebug()
    {
        return $this->debug;
    }

    /**
     * @param boolean $debug
     */
    public function setDebug($debug)
    {
        $this->debug = $debug;
    }

    /**
     * @return boolean
     */
    public function isTrack()
    {
        return $this->track;
    }

    /**
     * @param boolean $track
     */
    public function setTrack($track)
    {
        $this->track = $track;
    }

    /**
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * @param string $url
     */
    public function setUrl($url)
    {
        $index = strpos($url, "?");
        if ($index === false) {
            $this->url = $url;
        } else {
            $prefix = substr($url, 0, $index);
            $this->url = $prefix;
            $postfix = substr($url, $index + 1);
            $args = self::toArgs($postfix);
            $this->add_parameters($args);
        }
    }

    /**
     * @param $string
     * @return array
     */
    public static function toArgs($string)
    {
        $args = [];
        $temp = explode("&", $string);
        foreach ($temp as $item) {
            $cell = explode("=", $item);
            if (count($cell) == 1) {
                $args[$cell[0]] = "";
            } else {
                $args[$cell[0]] = urldecode($cell[1]);
            }
        }
        return $args;
    }

    /**
     * @return string
     */
    public function getBaseUrl()
    {
        return $this->baseUrl;
    }

    /**
     * @param string $baseUrl
     */
    public function setBaseUrl($baseUrl)
    {
        $this->baseUrl = $baseUrl;
    }

    /**
     * @return array
     */
    public function getFiles()
    {
        return $this->files;
    }

    /**
     * @param array $files
     */
    public function setFiles($files)
    {
        foreach ($files as $file) {
            if (is_string($file)) {
                $this->addFile($file);
                return;
            }
            $this->files[] = $file;
        }
    }

    public function addFile($file, $file_name = "", $post_name = "")
    {
        if (empty($file_name)) {
            $file_name = basename($file);
        }
        if (empty($post_name)) {
            $post_name = basename($file);
        }
        $bean = array("file_name" => $file_name, "file" => $file, "post_name" => $post_name);
        $this->debug("添加文件", $file);
        array_push($this->files, $bean);
    }

    /**
     * @return string
     */
    public function getParameterType()
    {
        return $this->parameterType;
    }

    /**
     * @param string $parameterType
     */
    public function setParameterType($parameterType)
    {
        $this->parameterType = $parameterType;
    }

    public function useJsonParameterType()
    {
        $this->setParameterType(HttpClient::PARAMETER_TYPE_JSON);
    }

    public function isJsonParameterType()
    {
        return $this->getParameterType() === HttpClient::PARAMETER_TYPE_JSON;
    }

    public function useNormalParameterType()
    {
        $this->setParameterType(HttpClient::PARAMETER_TYPE_NORMAL);
    }

    public function isNormalParameterType()
    {
        return $this->getParameterType() === HttpClient::PARAMETER_TYPE_NORMAL;
    }

    public function setJsonParameterType()
    {
        $this->useJsonParameterType();
    }

    public function pstJsonParams()
    {
        $this->setJsonParameterType();
    }

    public function postTraditionParams()
    {
        $this->setParameterType(HttpClient::PARAMETER_TYPE_NORMAL);
    }

    public function postXmlParams()
    {
        $this->setParameterType(HttpClient::PARAMETER_TYPE_XML);
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }

    /**
     * @param string $method
     */
    public function setMethod($method)
    {
        $this->method = $method;
    }


    public function startsWith($string, $prefix)
    {
        if (strlen($string) < strlen($prefix)) {
            return false;
        }
        $len = strlen($prefix);
        return substr($string, 1, $len) == $prefix;
    }

    /**
     * @return int
     */
    public function getExecuteTimeoutSeconds()
    {
        return $this->executeTimeoutSeconds;
    }

    /**
     * @param int $executeTimeoutSeconds
     */
    public function setExecuteTimeoutSeconds($executeTimeoutSeconds)
    {
        $this->executeTimeoutSeconds = $executeTimeoutSeconds;
    }

    /**
     * @return int
     */
    public function getConnectTimeoutSeconds()
    {
        return $this->connectTimeoutSeconds;
    }

    /**
     * @param int $connectTimeoutSeconds
     */
    public function setConnectTimeoutSeconds($connectTimeoutSeconds)
    {
        $this->connectTimeoutSeconds = $connectTimeoutSeconds;
    }

    public function addSSLCert($keyFile, $certFile)
    {
        $this->sslCerts[$keyFile] = $certFile;
    }

    /**
     * @return bool
     */
    public function isSslVerifyHost()
    {
        return $this->sslVerifyHost;
    }

    /**
     * @param bool $sslVerifyHost
     */
    public function setSslVerifyHost($sslVerifyHost)
    {
        $this->sslVerifyHost = $sslVerifyHost;
    }

    /**
     * @return bool
     */
    public function isSslVerifyPeer()
    {
        return $this->sslVerifyPeer;
    }

    /**
     * @param bool $sslVerifyPeer
     */
    public function setSslVerifyPeer($sslVerifyPeer)
    {
        $this->sslVerifyPeer = $sslVerifyPeer;
    }

    /**
     * @return string
     */
    public function getCurlLogFile()
    {
        return $this->curlLogFile;
    }

    /**
     * @param string $curlLogFile
     */
    public function setCurlLogFile($curlLogFile)
    {
        $this->curlLogFile = $curlLogFile;
    }


}
